#include "../include/modules/tower.h"
#include "../lib/GM/include/geometry/Vector3D.h"
#include "../lib/GM/include/geometry/Point3D.h"
#include "../lib/GM/include/geometry/geometry.h"
#include <stdio.h>
#include <stdlib.h>
#include "../include/modules/sprite.h"

#define EPSILON 0.000001
#define PRICE_ROCKET 250
#define PRICE_LASER 400
#define PRICE_HYBRID 200
#define PRICE_MACHINEGUN 500

Tower* createTower(){
	Tower *t = (Tower *)malloc(sizeof(Tower));
	if(t == NULL){
		fprintf(stderr, "createTower::Erreur de malloc de tower\n");
		return NULL;
	}
	t->type = AUCUN;
	t->coord.x = 0;
	t->coord.y = 0;
	t->width = 0;
	t->height = 0;
	t->texture = NULL;
	t->lvl = 0;
	t->damage = 0;
	t->rangePx = 0;
	t->rangeGL = 0;
	t->cadence = 0;
	t->price = 0;
	t->priceUpgrade = 0;
	t->isSet = 0;
	t->target.monster = NULL;
	t->target.distanceMtoT = 0;
	t->timerShoot = 0;
	t->select = 0;
	t->sprite = createSprite(t->sprite);
	return t;
}

Tower* initTower(TypeTower type, GLuint *textureTower, unsigned int WINDOW_WIDTH, unsigned int WINDOW_HEIGHT, int squareSize){
	if(textureTower == NULL){
		fprintf(stderr, "initTower:: textureTower n'existe pas\n");
		return NULL;
	}
	Tower *t = createTower();
	if(t == NULL){
		fprintf(stderr, "initTower:: t n'existe pas\n");
		return NULL;
	}
	t->texture = textureTower;
	t->type = type;
	t->coord.x = 0;
	t->coord.y = 0;
	t->lvl = 1;
	t->target.monster = NULL;
	t->target.distanceMtoT = 0;
	t->select = 0;
	switch(type){
		case ROCKET:
			t->width = SDLtoOpenGL(squareSize, WINDOW_WIDTH, ABSCISSA);
			t->height = SDLtoOpenGL(squareSize, WINDOW_HEIGHT, ORDERED);
			t->damage = 15;
			t->rangePx = 95;
			t->rangeGL = SDLtoOpenGL(t->rangePx, WINDOW_WIDTH, ABSCISSA);
			// printf("rangeGL ROCKET %f\n",t->rangeGL);
			t->cadence = 15;
			t->price = PRICE_ROCKET;
			t->priceUpgrade = (int)((t->price)/2);
			t->timerShoot = t->cadence;
			t->sprite = initSprite(t->sprite, t->texture, t->width, t->height, t->coord, 3, 3);
			break;

		case LASER:
			t->width = SDLtoOpenGL(squareSize, WINDOW_WIDTH, ABSCISSA);
			t->height = SDLtoOpenGL(squareSize, WINDOW_HEIGHT, ORDERED);
			t->damage = 6;
			t->rangePx = 65;
			t->rangeGL = SDLtoOpenGL(t->rangePx, WINDOW_WIDTH, ABSCISSA);
			// printf("rangeGL LASER %f\n",t->rangeGL);
			t->cadence = 3;
			t->price = PRICE_LASER;
			t->priceUpgrade = (int)((t->price)/2);
			t->timerShoot = t->cadence;
			t->sprite = initSprite(t->sprite, t->texture, t->width, t->height, t->coord, 2, 3);
			break;

		case HYBRID:
			t->width = SDLtoOpenGL(squareSize, WINDOW_WIDTH, ABSCISSA);
			t->height = SDLtoOpenGL(squareSize, WINDOW_HEIGHT, ORDERED);
			t->damage = 3;
			t->rangePx = 85;
			t->rangeGL = SDLtoOpenGL(t->rangePx, WINDOW_WIDTH, ABSCISSA);
			// printf("rangeGL HYBRIDE %f\n",t->rangeGL);
			t->cadence = 10;
			t->price = PRICE_HYBRID;
			t->priceUpgrade = (int)((t->price)/2);
			t->timerShoot = t->cadence;
			t->sprite = initSprite(t->sprite, t->texture, t->width, t->height, t->coord, 2, 3);
			break;

		case MACHINEGUN:
			t->width = SDLtoOpenGL(squareSize, WINDOW_WIDTH, ABSCISSA);
			t->height = SDLtoOpenGL(squareSize, WINDOW_HEIGHT, ORDERED);
			t->damage = 3;
			t->rangePx = 55;
			t->rangeGL = SDLtoOpenGL(t->rangePx, WINDOW_WIDTH, ABSCISSA);
			// printf("rangeGL MACHINEGUN %f\n",t->rangeGL);
			t->cadence = 4;
			t->price = PRICE_MACHINEGUN;
			t->priceUpgrade = (int)((t->price)/2);
			t->timerShoot = t->cadence;
			t->sprite = initSprite(t->sprite, t->texture, t->width, t->height, t->coord, 2, 3);
			break;

		default:
			fprintf(stderr, "initTower::Type de tour non reconnu\n");
			break;
	}
	return t;
}

List addTower(List listTower, TypeTower type, GLuint *textureTower, unsigned int WINDOW_WIDTH, unsigned int WINDOW_HEIGHT, int squareSize, CoordOpenGL mouseGL){
	Tower *t = initTower(type, textureTower, WINDOW_WIDTH, WINDOW_HEIGHT, squareSize);
	positionTower(t, mouseGL);		// la tour prend les dernieres coordonnées que la souris à touchée
	listTower = appendList(listTower, t);	//on ajoute la tour à la liste
    return listTower;
}

int getTowerType(Tower *t){
	if(t == NULL){
		fprintf(stderr, "getTowerType::Erreur la tour n'existe pas.\n");
		return 0;
	}
    return t->type;
}

void positionTower(Tower *t ,CoordOpenGL coord){
	if(t == NULL){
		fprintf(stderr, "positionTower::Il n'y a pas de tour\n");
		return;
	}
	t->coord.x = coord.x + t->width/2;	//ajouter la msquareSizeoitié de la taille du carré pour positionner les coord au centre et bien caler la portée
	t->coord.y = coord.y + t->height/2;
	updatePosSprite(&(t->sprite), t->coord); // Met à jour la position du sprite.
}

List buyTower(Player *player, List listTower, TypeTower type, GLuint *textureTower, unsigned int WINDOW_WIDTH, unsigned int WINDOW_HEIGHT, int squareSize, CoordOpenGL mouseGL){
	int price;
	switch(type){
		case ROCKET:
			price = PRICE_ROCKET;
			if(player->money >= price){
				player->constructMode = 1; 	// On passe en mode construction
				listTower = addTower(listTower, type, textureTower, WINDOW_WIDTH, WINDOW_HEIGHT, squareSize, mouseGL);
			}
			else{
				printf("Vous n'avez pas assez d'argent\n");
			}
			break;
		case LASER:
			price = PRICE_LASER;
			if(player->money >= price){
				player->constructMode = 1; 	// On passe en mode construction
				listTower = addTower(listTower, type, textureTower, WINDOW_WIDTH, WINDOW_HEIGHT, squareSize, mouseGL);
			}
			else{
				printf("Vous n'avez pas assez d'argent\n");
			}
			break;
		case HYBRID:
			price = PRICE_HYBRID;
			if(player->money >= price){
				player->constructMode = 1; 	// On passe en mode construction
				listTower = addTower(listTower, type, textureTower, WINDOW_WIDTH, WINDOW_HEIGHT, squareSize, mouseGL);
			}
			else{
				printf("Vous n'avez pas assez d'argent\n");
			}
			break;
		case MACHINEGUN:
			price = PRICE_MACHINEGUN;
			if(player->money >= price){
				player->constructMode = 1; 	// On passe en mode construction
				listTower = addTower(listTower, type, textureTower, WINDOW_WIDTH, WINDOW_HEIGHT, squareSize, mouseGL);
			}
			else{
				printf("Vous n'avez pas assez d'argent\n");
			}
			break;
		default:
			break;
	}
	return listTower;
}

void drawTower(Tower *t){
	drawSprite(&(t->sprite));
}

void drawRange(Tower *t){
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glPushMatrix();
	    glColor4f (1.0, 1.0, 1.0, 0.35);
	    glTranslatef((t->coord.x), (t->coord.y), 0);
	    glScalef(t->rangeGL, t->rangeGL, 0);
	    drawCircle();			// cercle full
	    glColor4f (1.0, 1.0, 1.0, 1.0);
	    drawOutlineCircle();	// contour du cercle
    glPopMatrix();
}

float distanceTowerMonster(Tower *tower, Monster *monster){
	if(tower == NULL || monster == NULL){
		fprintf(stderr, "distanceTowerMonster::la tour ou le monster n'existe pas\n");
		return 0;
	}
	Point3D A, B;
	Vector3D AB;
	float normAB; 

	A = PointXYZ(tower->coord.x, tower->coord.y, 0);	// Point de départ (position actuelle du monstre).
	B = PointXYZ(monster->pos.x, monster->pos.y, 0);	// Point d'arrivé (position du noeud à atteindre).
	AB = Vector(A, B);					
	normAB = Norm(AB);		//calcul de la norme == distance
	return normAB;
}

float min(MonsterDistance tab[], int lenghtTab){
	float min = 0;
	if(lenghtTab > 0){
	   min = tab[0].distanceMtoT;		//On prend la premiere valeur du tableau comme une reference pour pouvoir comparer
	   for (int i=1; i<lenghtTab; i++){
			if(tab[i].distanceMtoT<min){
				min = tab[i].distanceMtoT;
			} 
	    }
	}
	return min;
}

void updateTarget(Tower *tower, List listMonster){
	if(tower == NULL){
		fprintf(stderr, "updateTarget::tower n'existe pas\n");
		return;
	}
	if(listMonster == NULL){	// si il n'y a pas de monstre pas besoin de updateTarget
		return;
	}
	if(tower->isSet == 0){		// empeche une tour non placée de tirer sur des monstres
		return;
	}
	/* Si la tour n'a pas de cible OU que la distance de sa cible n'est plus dans sa portée */
	if(tower->target.monster == NULL || tower->target.distanceMtoT > tower->rangeGL){
		int lenght = 0;
		int nbMonster = lengthList(listMonster);
		MonsterDistance tabDistance[nbMonster];
		float distanceMin = 0;

		foreachList(node, listMonster){
			Monster *m = (Monster *)node->data;
			float distance = distanceTowerMonster(tower, m);
			if(distance - tower->rangeGL < EPSILON){			//si la distance est plus petite que la range
				tabDistance[lenght].monster = m;				//on enregistre le monstre concerné
				tabDistance[lenght].distanceMtoT = distance;	//ainsi que sa distance avec la tour
				lenght++;
			}
			
		}
		if(lenght > 0){
			distanceMin = min(tabDistance, lenght);		//on trouve la distance min parmis tt les montres de la liste
			for(int i = 0; i<lenght; i++){
				if(tabDistance[i].distanceMtoT - distanceMin < EPSILON){	//si la distance qu'on verifie est égale à la distance min
					tower->target = tabDistance[i]; // Changement de target si une target trouvé est à la portée de ma tour.
				}
			}
		}
		else{											//plus aucun monstre n'est à sa portée
			tower->target.monster = NULL;
		}
	}
	/* si la tour à deja une target ET que la distance de sa target est inférieure à son range */
	else if(tower->target.monster != NULL && tower->target.distanceMtoT - tower->rangeGL < EPSILON){
		tower->target.distanceMtoT = distanceTowerMonster(tower, tower->target.monster);	//on recalcule la distance du monstre qui s'est déplacé
	}
}

int shootMonster(Tower *tower){
	if(tower == NULL){
		fprintf(stderr, "shootMonster:: un pointeur n'existe pas\n");
		return 0;
	}
	if(tower->target.monster != NULL){		//si la tour à une cible
		if(tower->timerShoot == 0){			//si le timer de cadence de tir correspond au moment de tir de la tour
			int damage = tower->damage;
			switch(tower->type){
				case ROCKET:
					damage = damage - getResistRocketMonster(tower->target.monster);
					break;
				case LASER:
					damage = damage - getResistLaserMonster(tower->target.monster);
					break;
				case HYBRID:
					damage = damage - getResistHybridMonster(tower->target.monster);
					break;
				case MACHINEGUN:
					damage = damage - getResistMachineGunMonster(tower->target.monster);
					break;
				default:
					fprintf(stderr, "Le type de la tour n'est pas connu.\n");
					break;
			}
			if(damage>0){
				setLifeMonster(tower->target.monster, (-damage));
			}			
			tower->timerShoot = tower->cadence;
			if(tower->target.monster->life < 0){
				return 1;
			}
			switch(tower->lvl){		// en fonction du niveau de la tour on selectionne le bon sprite
				case 1:
					updateStateSprite(&(tower->sprite), DOWN);
					break;
				case 2:
					updateStateSprite(&(tower->sprite), LEFT);
					break;
				case 3:
					updateStateSprite(&(tower->sprite), RIGHT);
					break;
				default:
					fprintf(stderr, "Level de la tour non pris en charge.\n");
					break;
			}
		}
		else{
			switch(tower->lvl){
				case 1:
					updateStateSprite(&(tower->sprite), DOWN);
					break;
				case 2:
					updateStateSprite(&(tower->sprite), LEFT);
					break;
				case 3:
					updateStateSprite(&(tower->sprite), RIGHT);
					break;
				default:
					fprintf(stderr, "Level de la tour non pris en charge.\n");
					break;
			}
			tower->timerShoot--; 	// Décompte de timerShoot (cadence de tr de la tour)
		}
	}
	else{
		tower->sprite.anim = 0; // Met l'image de base si la tour n'a pas de target.
	}
	return 0;
}

void updateTowerLevel(Tower *t, unsigned int WINDOW_WIDTH){
	if(t->lvl < 3){			// upgrade possible que si on est pas au dernier niveau d'updrage
		t->price += t->priceUpgrade;
		t->priceUpgrade += (int)((t->price)/2);
		t->lvl += 1;
		switch(t->lvl){
			case 1:
				updateStateSprite(&(t->sprite), DOWN);
				t->sprite.anim = 0;
				break;
			case 2:
				updateStateSprite(&(t->sprite), LEFT);
				t->sprite.anim = 0;
				break;
			case 3:
				t->priceUpgrade = 0;
				updateStateSprite(&(t->sprite), RIGHT);
				t->sprite.anim = 0;
				break;
			default:
				fprintf(stderr, "Level de la tour non pris en charge.\n");
				break;
		}
		t->rangePx += 15;
		t->rangeGL = SDLtoOpenGL(t->rangePx, WINDOW_WIDTH, ABSCISSA);
		t->damage += 5;
	}
	else{
		printf("Vous etes deja au maximum d'upgrade.\n");
	}
}

List deleteTower(Tower *t, List *listTower){
	if(t == NULL || listTower == NULL){
		fprintf(stderr, "deleteTower:: t ou listTower n'existe pas\n");
		return *listTower;
	}
	*listTower = removeFromList(*listTower, t);
	return *listTower;
}